﻿using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
using System.Security;
using System.Reflection;
using System.Threading;

[assembly: AllowPartiallyTrustedCallers]
namespace CoreMotivesWebPart
{
	[ToolboxItemAttribute(false)]
	public class CoreMotivesTracking : WebPart, IWebEditable
	{
		// Regular expression for validating Guids
		private const String GUID_REGEX = @"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$";

		// Address for CoreMotives tracking
		private const String COREMOTIVES_REQUEST_URI = "http://databroker-us.coremotives.com/WebCapture.aspx";

		private String _crmInstanceId; // CRM Instance ID
		private String _cmCustomerId; // CoreMotives Customer ID
		private Boolean _ignoreCertificateErrors = false; // Flag to ignore certificate errors.




		protected override void Render(HtmlTextWriter writer)
		{
			// check configuration.
			String validationString = ValidateConfiguration();
			if (!String.IsNullOrEmpty(validationString))
			{
				// if configuration not complete or valid, notify user
				writer.Write(validationString);
				return;
			}

			// if page is not a postback, log request
			if (!Page.IsPostBack)
			{
				SetCookies();
				String parameters = BuildRequestParameters();
				LogRequest(parameters);
			}
		}


		#region #### Helper Methods ####

		/// <summary>
		/// Checks to make sure that all configuration data is present and valid.
		/// </summary>
		/// <returns>String: empty if no configuration errors, error information if configuration incomplete or invalid</returns>
		private String ValidateConfiguration()
		{
			StringBuilder returnText = new StringBuilder();

			// validate crmInstanceId
			if (!ValidateInt(_crmInstanceId))
			{
				returnText.Append("Please supply valid Integer value for CRM Instance Id.<br/>");
			}

			// validate cmCustomerId
			if (!ValidateGuid(_cmCustomerId))
			{
				returnText.Append("Please supply valid GUID value for CoreMotives Customer Id (format: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})<br/>");
			}

			return returnText.ToString();
		}

		/// <summary>
		/// Validates an in value.
		/// </summary>
		/// <param name="_crmInstanceId"></param>
		/// <returns></returns>
		private bool ValidateInt(string _crmInstanceId)
		{
			Int32 temp;
			return Int32.TryParse(_crmInstanceId, out temp);
		}

		/// <summary>
		/// Validates whether the supplied string is a valid guid in an 8-4-4-4-12 format
		/// </summary>
		/// <param name="guid">Guid to be validated</param>
		/// <returns>Boolean</returns>
		private bool ValidateGuid(String guid)
		{
			if (!String.IsNullOrEmpty(guid))
			{
				Regex guidRegEx = new Regex(GUID_REGEX);

				return guidRegEx.IsMatch(guid);
			}
			return false;
		}

		/// <summary>
		/// Checks to see if CoreMotives cookies present. If they are not, then they
		/// are preated and populated.
		/// </summary>
		private void SetCookies()
		{
			if (Page.Request.Cookies["CmVisitor"] == null)
			{
				HttpCookie cmCookie = new HttpCookie("CmVisitor");
				cmCookie.Value = Guid.NewGuid().ToString();
				cmCookie.Expires = DateTime.Now.AddYears(20);
				Page.Response.Cookies.Add(cmCookie);
			}

			if (Page.Request.Cookies["CmSession"] == null)
			{
				HttpCookie sessionCookie = new HttpCookie("CmSession");
				sessionCookie.Value = Guid.NewGuid().ToString();
				Page.Response.Cookies.Add(sessionCookie);
			}
		}

		/// <summary>
		/// Builds request parameters to be logged by CoreMotives
		/// </summary>
		/// <returns></returns>
		private String BuildRequestParameters()
		{
			String visitorId = Page.Request.Cookies["CmVisitor"].Value;
			String sessionId = Page.Request.Cookies["CmSession"].Value;
			String url = Page.Request.Url.AbsoluteUri;
			String referringUrl = (Page.Request.UrlReferrer != null ? Page.Request.UrlReferrer.AbsoluteUri : String.Empty);
			String userAgentString = Page.Request.UserAgent;
			
			String parameters = "cm_cid={0}&cm_iid={1}&cm_vid={2}&cm_sid={3}&cm_url={4}";
			parameters = String.Format(parameters, _cmCustomerId, _crmInstanceId, visitorId, sessionId, url);

			if (!String.IsNullOrEmpty(referringUrl))
			{
				parameters += "&cm_ref=" + referringUrl;
			}

			// Add referring IP address into passed parameters. This way CoreMotives will properly track
			// which IP is making the actual request. Jeremie at CoreMotives will have supporting functionality
			// in place by 8/15/11. This has been put into place because non-standard HTTP request headers were
			// being stripped off outgoing requests by our organization's firewalls. -- sean 8/10/11
			String ipAddress = GetRequestIPAddress();
			if (!String.IsNullOrEmpty(ipAddress))
			{
				parameters += "&cm_ipaddress=" + ipAddress;
			}

			return parameters;
		}

		/// <summary>
		/// Retrieves the IP address for the referring page.
		/// </summary>
		/// <returns></returns>
		private String GetRequestIPAddress()
		{
			// get request IP address
			String ipAddress = Page.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
			if (String.IsNullOrEmpty(ipAddress)) ipAddress = Page.Request.ServerVariables["HTTP_X_CLUSTER_CLIENT_IP"];
			if (String.IsNullOrEmpty(ipAddress)) ipAddress = Page.Request.ServerVariables["REMOTE_ADDR"];

			// HTTP_X_FORWARDED_FOR can sometimes have a comma delimited list of IP addresses
			return ipAddress.Split(',')[0].Trim();

		}

		/// <summary>
		/// Logs http request with CoreMotives web service using the provided parameters
		/// </summary>
		/// <param name="parameters">All post parameters to be logged</param>
		private void LogRequest(String parameters)
		{

			// create logger
			CMLoggingWorker logger = new CMLoggingWorker(_ignoreCertificateErrors);
			logger.CoreMotivesRequestUri = COREMOTIVES_REQUEST_URI;
			logger.Parameters = parameters;
			logger.UserAgent = Page.Request.UserAgent;
			Thread loggerThread = new Thread(logger.LogPageRequest);
			loggerThread.Start();

		}

		//####
		// perhaps more time in here would lead to a better solution
		// to the certificate error problem
		//
		//        public static bool MyCertValidationCb(
		//      object sender, 
		//      X509Certificate certificate, 
		//      X509Chain chain, 
		//      SslPolicyErrors sslPolicyErrors)
		//{
		//  if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors)
		//            == SslPolicyErrors.RemoteCertificateChainErrors)
		//  {
		//    return false;
		//  }
		//  else if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch)
		//                  == SslPolicyErrors.RemoteCertificateNameMismatch)
		//  {
		//    System.Security.Policy.Zone z;
		//    z = System.Security.Policy.Zone.CreateFromUrl(((HttpWebRequest)sender).RequestUri.ToString());
		//    if (z.SecurityZone == System.Security.SecurityZone.Intranet
		//      || z.SecurityZone == System.Security.SecurityZone.MyComputer)
		//    {
		//      return true;
		//    }
		//    return false;
		//  }
		//  return false;
		//} 

		//####

		#endregion



		#region #### Getters and Setters ####

		/// <summary>
		/// CRM Instance Id
		/// 
		/// String representation of GUID in 8-4-4-4-12 format
		/// </summary>
		[WebBrowsable(true),
		Personalizable(PersonalizationScope.Shared),
		WebDisplayName("CRM Instance Id"),
		WebDescription("Enter CRM instance Id (guid value)"),
		Category("Configuration")]
		public String CrmInstanceId
		{
			get { return this._crmInstanceId; }
			set { this._crmInstanceId = value.Trim(); }
		}

		/// <summary>
		/// CoreMotives Customer Id
		/// 
		/// String representation of GUID in 8-4-4-4-12 format
		/// </summary>
		[WebBrowsable(true),
		Personalizable(PersonalizationScope.Shared),
		WebDisplayName("CoreMotives Customer Id"),
		WebDescription("Enter CoreMotives Customer Id (guid value)"),
		Category("Configuration")]
		public String CMCustomerId
		{
			get { return this._cmCustomerId; }
			set { this._cmCustomerId = value.Trim(); }
		}

		/// <summary>
		/// Sets property to ignore or pay attention to ssl certificate errors that may
		/// occur while web part submits data to CoreMotives. Set to false by default.
		/// </summary>
		[WebBrowsable(true),
		Personalizable(PersonalizationScope.Shared),
		WebDisplayName("Ignore Certificate Errors"),
		WebDescription("If certificate errors are being thrown while trying the web part is attempting to submit data to CoreMotives, this will disable those errors if set to true."),
		Category("Configuration")]
		public Boolean IgnoreCertificateErrors
		{
			get { return this._ignoreCertificateErrors; }
			set { this._ignoreCertificateErrors = value; }
		}

		#endregion


	}
}

